﻿using System;
using System.Globalization;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.RelyingParty;
using SacNetUG.Helpers;
using SacNetUG.Models;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;

namespace SacNetUG.Controllers
{
    [HandleError]
    public partial class AccountController : Controller
    {
        private readonly RegisterModel _viewModel = new RegisterModel();
        // This constructor is used by the MVC framework to instantiate the controller using
        // the default forms authentication and membership providers.

        public AccountController()
            : this(null, null)
        {
            repository = new SacNetUGRepository();
        }

        // This constructor is not used by the MVC framework but is instead provided for ease
        // of unit testing this type. See the comments at the end of this file for more
        // information.
        public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationService();
            MembershipService = service ?? new AccountMembershipService();
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public IMembershipService MembershipService
        {
            get;
            private set;
        }

        public virtual ActionResult LogOn()
        {
            var returnUrl = Request["ReturnUrl"];
            if (!String.IsNullOrEmpty(returnUrl))
            {
                Session["ReturnUrl"] = returnUrl;
            }
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()")]
        public virtual ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
        {

            if (!ValidateLogOn(userName, password))
            {
                return View();
            }

            FormsAuth.SignIn(userName, rememberMe);
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return RedirectToAction("Index", "Home");
        }

        public virtual ActionResult LogOff()
        {
            FormsAuth.SignOut();

            //send a sign out to windows live if they are a live user

            return RedirectToAction("Index", "Home");
        }

        public virtual ActionResult Register()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View();
        }

        #region LiveID Methods
        [HttpPost]
        public virtual ActionResult LiveId()
        {
            var action = Request.Form["action"];

            if (action == "login")
            {
                var windowsLiveLogin = new WindowsLiveLogin(true);
                WindowsLiveLogin.User user = windowsLiveLogin.ProcessLogin(Request.Form);
                if (user != null)
                {
                    if (repository.IsUserRegistered(user.Id))
                    {
                        FormsAuth.SignIn(user.Id, true/*rememberMe*/);

                        var returnUrl = Session["ReturnUrl"];

                        if (returnUrl != null)
                        {
                            return Redirect(returnUrl.ToString());
                        }

                        return RedirectToAction(MVC.Home.Index());
                    }
                    ViewData["authId"] = user.Id;
                }

                return View("Register");
            }

            return View("Error");
        }
        [HttpPost]
        public virtual ActionResult LiveIdRegistration(RegisterModel registerModel)
        {
            var createStatus = MembershipService.CreateUser(registerModel.AuthId, registerModel.AuthId, registerModel.Email);
            if (createStatus == MembershipCreateStatus.Success)
            {
                repository.AddUser(registerModel);
                FormsAuth.SignIn(registerModel.AuthId, true /* create PersistentCookie */);
                return RedirectToAction(MVC.Home.Index());
            }
            ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
            return View("Register");
        }
        #endregion
        #region OpenId Methods

        [AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
        public virtual ActionResult OpenIdLogOn(string returnUrl)
        {
            var openid = new OpenIdRelyingParty();
            var response = openid.GetResponse();
            if (response == null)  // Initial operation
            {
                // Step 1 - Send the request to the OpenId provider server
                Identifier id;
                if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
                {
                    try
                    {
                        var req = openid.CreateRequest(Request.Form["openid_identifier"]);
                        return req.RedirectingResponse.AsActionResult();
                    }
                    catch (ProtocolException ex)
                    {
                        // display error by showing original LogOn view
                        ViewData["Message"] = ex.Message;
                        return View("Logon");
                    }
                }
                // display error by showing original LogOn view
                ViewData["Message"] = "Invalid identifier";
                return View("LogOn");
            }
            // Step 2: OpenID Provider sending assertion response
            switch (response.Status)
            {
                case AuthenticationStatus.Authenticated:
                    var user = repository.GetUserByOpenId(response);
                    if (user != null)
                    {
                        FormsAuth.SignIn(user.AuthID, true /* create PersistentCookie */);
                        if (Session["ReturnUrl"] != null)
                        {
                            returnUrl = Session["ReturnUrl"].ToString();
                            if (!string.IsNullOrEmpty(returnUrl))
                            {
                                return Redirect(returnUrl);
                            }
                        }
                        return RedirectToAction("Index", "Home");
                    }

                    var registerModel = new RegisterModel { AuthId = response.ClaimedIdentifier };
                    return View("RegisterOpenID", registerModel);
                case AuthenticationStatus.Canceled:
                    ViewData["Message"] = "Canceled at provider";
                    return View("LogOn");
                case AuthenticationStatus.Failed:
                    ViewData["Message"] = response.Exception.Message;
                    return View("LogOn");
            }
            return new EmptyResult();
        }

        [AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
        public virtual ActionResult OpenIdRegistration(RegisterModel registerModel)
        {
            if (repository.GetUserByOpenId(registerModel.AuthId) != null)
            {
                ViewData["Message"] = "OpenId already in use";
                _viewModel.AuthId = string.Empty;
                return View("LogOn");
            }

            var createStatus = MembershipService.CreateUser(registerModel.AuthId, registerModel.AuthId, registerModel.Email);
            if (createStatus == MembershipCreateStatus.Success)
            {
                repository.AddUser(registerModel);
                FormsAuth.SignIn(registerModel.AuthId, true /* create PersistentCookie */);
                return RedirectToAction(MVC.Home.Index());
            }
            ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
            return View("Register");
        }

        #endregion

        #region Validation Methods

        private bool ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {
            if (String.IsNullOrEmpty(currentPassword))
            {
                ModelState.AddModelError("currentPassword", "You must specify a current password.");
            }
            if (newPassword == null || newPassword.Length < MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("newPassword",
                                         String.Format(CultureInfo.CurrentCulture,
                                                       "You must specify a new password of {0} or more characters.",
                                                       MembershipService.MinPasswordLength));
            }

            if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "You must specify a username.");
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError("password", "You must specify a password.");
            }
            if (!MembershipService.ValidateUser(userName, password))
            {
                ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateRegistration(string userName, string email, string password, string confirmPassword)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "You must specify a username.");
            }
            if (String.IsNullOrEmpty(email))
            {
                ModelState.AddModelError("email", "You must specify an email address.");
            }
            if (password == null || password.Length < MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("password",
                                         String.Format(CultureInfo.CurrentCulture,
                                                       "You must specify a password of {0} or more characters.",
                                                       MembershipService.MinPasswordLength));
            }
            if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.");
            }
            return ModelState.IsValid;
        }

        private static string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                default:
                    return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
            }
        }
        #endregion
    }

    public interface IFormsAuthentication
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : IFormsAuthentication
    {
        public void SignIn(string userName, bool createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string password, string email);
        bool ChangePassword(string userName, string oldPassword, string newPassword);
    }

    public class AccountMembershipService : IMembershipService
    {
        private readonly MembershipProvider _provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }

        public int MinPasswordLength
        {
            get
            {
                return _provider.MinRequiredPasswordLength;
            }
        }

        public bool ValidateUser(string userName, string password)
        {
            return _provider.ValidateUser(userName, password);
        }

        public MembershipCreateStatus CreateUser(string userName, string password, string email)
        {
            MembershipCreateStatus status;
            _provider.CreateUser(userName, password, email, null, null, true, null, out status);
            return status;
        }

        public bool ChangePassword(string userName, string oldPassword, string newPassword)
        {
            MembershipUser currentUser = _provider.GetUser(userName, true /* userIsOnline */);
            return currentUser.ChangePassword(oldPassword, newPassword);
        }
    }
}